package org.example.liuy.service;

import lombok.extern.slf4j.Slf4j;
import org.example.liuy.exception.BusinessException;

import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;

/**
 * 监控服务 - 使用代理模式
 */
@Slf4j
public class MonitoringService {

    private static final MonitoringService INSTANCE = new MonitoringService();

    private final AtomicLong totalRequests = new AtomicLong(0);
    private final Map<String, AtomicLong> methodCallCounters = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> methodSuccessCounters = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> methodErrorCounters = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> methodDurationSum = new ConcurrentHashMap<>();
    private ScheduledExecutorService monitoringScheduler;

    private MonitoringService() {}

    public static MonitoringService getInstance() {
        return INSTANCE;
    }

    public <T> T monitor(String methodName, Supplier<T> operation) {
        long startTime = System.nanoTime();
        incrementCounter(methodName, "total");
        totalRequests.incrementAndGet();

        try {
            T result = operation.get();
            long duration = (System.nanoTime() - startTime) / 1_000_000;
            recordSuccess(methodName, duration);
            log.debug("Method {} completed in {}ms", methodName, duration);
            return result;
        } catch (Exception e) {
            long duration = (System.nanoTime() - startTime) / 1_000_000;
            recordError(methodName, duration, e);
            log.error("Method {} failed after {}ms", methodName, duration, e);
            throw e;
        }
    }

    public void monitorVoid(String methodName, Runnable operation) {
        monitor(methodName, () -> {
            operation.run();
            return null;
        });
    }

    private void incrementCounter(String methodName, String type) {
        String counterName = methodName + "." + type;
        methodCallCounters.computeIfAbsent(counterName, k -> new AtomicLong(0)).incrementAndGet();
    }

    private void recordSuccess(String methodName, long duration) {
        methodSuccessCounters.computeIfAbsent(methodName, k -> new AtomicLong(0)).incrementAndGet();
        methodDurationSum.computeIfAbsent(methodName, k -> new AtomicLong(0)).addAndGet(duration);
    }

    private void recordError(String methodName, long duration, Exception e) {
        methodErrorCounters.computeIfAbsent(methodName, k -> new AtomicLong(0)).incrementAndGet();
    }

    public void startMonitoringTask() {
        monitoringScheduler = Executors.newSingleThreadScheduledExecutor();
        monitoringScheduler.scheduleAtFixedRate(this::logMetrics, 1, 1, TimeUnit.MINUTES);
    }

    private void logMetrics() {
        log.info("=== Performance Metrics ===");
        log.info("Total requests: {}", totalRequests.get());

        methodCallCounters.forEach((methodName, totalCalls) -> {
            if (methodName.endsWith(".total")) {
                String baseName = methodName.substring(0, methodName.length() - 6);
                long successCount = methodSuccessCounters.getOrDefault(baseName, new AtomicLong(0)).get();
                long errorCount = methodErrorCounters.getOrDefault(baseName, new AtomicLong(0)).get();
                long totalDuration = methodDurationSum.getOrDefault(baseName, new AtomicLong(0)).get();

                long avgDuration = totalCalls.get() > 0 ? totalDuration / totalCalls.get() : 0;
                double successRate = totalCalls.get() > 0 ?
                        (double) successCount / totalCalls.get() * 100 : 100.0;

                log.info("{}: calls={}, success={}, errors={}, success_rate={:.2f}%, avg_duration={}ms",
                        baseName, totalCalls.get(), successCount, errorCount, successRate, avgDuration);
            }
        });
    }

    public void stop() {
        if (monitoringScheduler != null) {
            monitoringScheduler.shutdown();
        }
    }
}


